package top.kpromise.note.modules.user.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import top.kpromise.common.base.Result;
import top.kpromise.common.utils.SecurityUtils;
import top.kpromise.note.config.CustomClientDetailsService;
import top.kpromise.note.data.Config;
import top.kpromise.note.modules.user.entity.UserEntity;
import top.kpromise.note.modules.user.model.LoginResult;
import top.kpromise.note.modules.user.service.LoginService;
import top.kpromise.note.modules.user.service.UserService;

import java.util.Collection;
import java.util.HashMap;

@Service
@Slf4j
public class LoginServiceImpl implements LoginService {

    private final CustomClientDetailsService customClientDetailsService;
    private final AuthenticationManager authenticationManager;
    private final DefaultTokenServices tokenServices;
    private final TokenStore tokenStore;
    private final UserService userService;
    private final AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;

    public LoginServiceImpl(CustomClientDetailsService customClientDetailsService,
                            AuthenticationManager authenticationManager, DefaultTokenServices tokenServices,
                            TokenStore tokenStore, UserService userService,
                            AuthorizationServerTokenServices defaultAuthorizationServerTokenServices) {
        this.customClientDetailsService = customClientDetailsService;
        this.authenticationManager = authenticationManager;
        this.tokenServices = tokenServices;
        this.tokenStore = tokenStore;
        this.userService = userService;
        this.defaultAuthorizationServerTokenServices = defaultAuthorizationServerTokenServices;
    }

    @Override
    public Result<LoginResult> login(UserEntity user) {
        if (user == null || user.getUserName() == null) {
            return Result.error("请输入用户名");
        }
        if (user.getPassword() == null) {
            return Result.error("请输入密码");
        }
        UserEntity loginUser = userService.findByUserName(user.getUserName());
        if (loginUser == null) {
            return Result.error("帐户不存在");
        }
        if (loginUser.getUserState() == Config.userStateLock) {
            return Result.error("账号已锁定,请联系管理员");
        }
        if (loginUser.getUserState() == Config.userStateCancel) {
            return Result.error("账号已注销,请联系管理员");
        }
        OAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = getNewOauthTokenByPassword(loginUser.getUserName(), user.getPassword());
        } catch (Exception e) {
            return Result.error("登录失败，密码错误");
        }
        if (oAuth2AccessToken == null) {
            return Result.error("登录失败，系统异常");
        }
        LoginResult loginResult = new LoginResult();
        loginResult.fromToken(oAuth2AccessToken);
        return Result.data(loginResult);
    }

    private OAuth2AccessToken getNewOauthTokenByPassword(String userAccount, String userPassWord) {

        logout(userAccount);

        Authentication usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userAccount,
                SecurityUtils.md5(userAccount, userPassWord));
        Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(buildLoginRequest(), authentication);
        oAuth2Authentication.setAuthenticated(true);
        return defaultAuthorizationServerTokenServices.createAccessToken(oAuth2Authentication);
    }

    private OAuth2Request buildLoginRequest() {
        String clientId = Config.clientId;
        ClientDetails clientDetails = customClientDetailsService.loadClientByClientId(clientId);
        return new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "password")
                .createOAuth2Request(clientDetails);
    }

    private TokenRequest buildRefreshTokenRequest() {
        String clientId = Config.clientId;
        ClientDetails clientDetails = customClientDetailsService.loadClientByClientId(clientId);
        return new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "refresh_token");
    }

    @Override
    public void logout(String userName) {
        Collection<OAuth2AccessToken> list = tokenStore.findTokensByClientIdAndUserName(Config.clientId, userName);
        if (list.isEmpty()) return;
        for (OAuth2AccessToken token : list) {
            log.debug("revokeToken for {} and token is {}", userName, token.getValue());
            tokenServices.revokeToken(token.getValue());
        }
    }

    @Override
    public Result<LoginResult> refreshToken(String refreshToken) {
        log.debug("refreshToken for {}", refreshToken);
        OAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = tokenServices.refreshAccessToken(refreshToken, buildRefreshTokenRequest());
        } catch (Exception e) {
            return Result.error("RefreshToken不存在或已过期");
        }
        if (oAuth2AccessToken == null) {
            return Result.error("系统异常");
        }
        LoginResult loginResult = new LoginResult();
        loginResult.fromToken(oAuth2AccessToken);
        return Result.data(loginResult);
    }
}
